﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Google.GData.Client;

namespace Google.Analytics.Model.Queries
{
    public class DataQuery : QueryBase
    {
        private const string queryUrl = "https://www.google.com/analytics/feeds/data";

        #region Public Properties
        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the row keys</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public IList<Dimensions> Dimensions { get; set; }

        // end of accessor public string Dimensions

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the last day for which to retrieve data 
        /// in form YYYY-MM-DD.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public DateTime GAEndDate { get; set; }

        // end of accessor public string EndDate

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the dimension value filters.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public string Filters { get; set; }

        // end of accessor public string Filters

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the Google Analytics profile ID, 
        /// prefixed by 'ga:'.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public string Ids { get; set; }

        // end of accessor public string Ids

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the comma separated list of numeric value 
        /// fields.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public IList<Metrics> Metrics { get; set; }

        // end of accessor public string Metrics

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the comma separated list of sort keys 
        /// in order of importance.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public IList<Order> Sort { get; set; }

        // end of accessor public string Sort

        //////////////////////////////////////////////////////////////////////
        /// <summary>Indicates the first day for which to retrieve data 
        /// in form YYYY-MM-DD.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public DateTime GAStartDate { get; set; }

        // end of accessor public string StartDate

        //////////////////////////////////////////////////////////////////////
        /// <summary>Adds extra whitespace to the feed XML to make it 
        /// more readable. This can be set to true or false, where the 
        /// default is false.</summary>
        /// <returns> </returns>
        //////////////////////////////////////////////////////////////////////
        public bool? PrettyPrint { get; set; }
        #endregion

        /// <summary>
        /// default constructor, does nothing 
        /// </summary>
        public DataQuery() : base(queryUrl)
        {
        }

        public DataQuery(AuthState state, string ID, DateTime startDate, DateTime endDate, IList<Metrics> metrics, IList<Dimensions> dimensions, IList<Order> order, string filter) : base(queryUrl)
        {
            AuthenticationState = state;
            Ids = ID;
            GAStartDate = startDate;
            GAEndDate = endDate;
            Metrics = metrics;
            Dimensions = dimensions;
            Sort = order;
            Filters = filter;
        }

        //////////////////////////////////////////////////////////////////////
        /// <summary>protected void ParseUri</summary> 
        /// <param name="targetUri">takes an incoming Uri string and parses all the properties out of it</param>
        /// <returns>throws a query exception when it finds something wrong with the input, otherwise returns a baseuri</returns>
        //////////////////////////////////////////////////////////////////////
        protected override Uri ParseUri(Uri targetUri)
        {
            base.ParseUri(targetUri);
            if (targetUri != null)
            {
                char[] deli = { '?', '&' };

                var tokens = new TokenCollection(targetUri.Query, deli);
                foreach (string token in tokens)
                {
                    if (token.Length > 0)
                    {
                        char[] otherDeli = { '=' };
                        String[] parameters = token.Split(otherDeli, 2);
                        switch (parameters[0])
                        {
                            case "dimensions":
                                Dimensions = DimensionsToString(parameters[1]);
                                break;
                            case "end-date":
                                GAEndDate = DateTime.ParseExact(parameters[1], "yyyy-MM-dd", CultureInfo.InvariantCulture);
                                break;
                            case "filters":
                                Filters = parameters[1];
                                break;
                            case "ids":
                                Ids = parameters[1];
                                break;
                            case "metrics":
                                Metrics = MetricsToString(parameters[1]);
                                break;
                            case "sort":
                                Sort = StringToStort(parameters[1]);
                                break;
                            case "start-date":
                                GAStartDate = DateTime.ParseExact(parameters[1], "yyyy-MM-dd", CultureInfo.InvariantCulture);
                                break;
                            case "prettyprint":
                                PrettyPrint = bool.Parse(parameters[1]);
                                break;
                        }
                    }
                }

        
            }
            return Uri;
        }


        //////////////////////////////////////////////////////////////////////
        /// <summary>Creates the partial URI query string based on all
        ///  set properties.</summary> 
        /// <returns> string => the query part of the URI </returns>
        //////////////////////////////////////////////////////////////////////
        protected override string CalculateQuery(string basePath)
        {
            string path = base.CalculateQuery(basePath);
            var newPath = new StringBuilder(path, 2048);
            char paramInsertion = InsertionParameter(path);  
			
            if (Dimensions != null && Dimensions.Count!=0)
            {
                AppendPath(newPath, ref paramInsertion, "dimensions={0}", Utilities.UriEncodeReserved(EnumToString(Dimensions)));
            }
            if (!GAEndDate.Equals(DateTime.MinValue))
            {
                AppendPath(newPath, ref paramInsertion, "end-date={0}", Utilities.UriEncodeReserved(GAEndDate.ToString("yyyy-MM-dd")));
            }
            if (!string.IsNullOrEmpty(Filters))
            {
                AppendPath(newPath, ref paramInsertion, "filters={0}", Utilities.UriEncodeReserved(Filters));
            }
            if (!string.IsNullOrEmpty(Ids))
            {
                AppendPath(newPath, ref paramInsertion, "ids={0}", Utilities.UriEncodeReserved(Ids));
            }
            if (Metrics != null && Metrics.Count!=0)
            {
                AppendPath(newPath, ref paramInsertion, "metrics={0}", Utilities.UriEncodeReserved(EnumToString(Metrics)));
            }
            if (Sort != null && Sort.Count != 0)
            {
                AppendPath(newPath, ref paramInsertion, "sort={0}", Utilities.UriEncodeReserved(SortToString(Sort)));
            }
            if (!GAStartDate.Equals(DateTime.MinValue))
            {
                AppendPath(newPath, ref paramInsertion, "start-date={0}", Utilities.UriEncodeReserved(GAStartDate.ToString("yyyy-MM-dd")));
            }
            if (PrettyPrint.HasValue)
            {
                AppendPath(newPath, ref paramInsertion, "prettyprint={0}", Utilities.UriEncodeReserved(PrettyPrint.Value.ToString().ToLower()));
            }
            return newPath.ToString();
        }

        private static void AppendPath(StringBuilder newPath, ref char paramInsertion, string format, string value)
        {
            newPath.Append(paramInsertion);
            newPath.AppendFormat(CultureInfo.InvariantCulture, format, value);
            paramInsertion = '&';
        }

        //todo: Rewrite to something more generic
        private static string EnumToString(IEnumerable<Metrics> enums)
        {
            var str = new StringBuilder();
            foreach (var en in enums)
            {
                str.AppendFormat("ga:{0},", en.ToString().ToLowerInvariant());
            }
            return str.ToString(0, str.Length-1);
        }

        private static string EnumToString(IEnumerable<Dimensions> enums)
        {
            var str = new StringBuilder();
            foreach (var en in enums)
            {
                str.AppendFormat("ga:{0},", en.ToString().ToLowerInvariant());
            }
            return str.ToString(0, str.Length - 1);
        }

        private static string SortToString(IEnumerable<Order> orders)
        {
            var str = new StringBuilder();
            foreach (var order in orders)
            {
                str.AppendFormat("ga:{0},", order.ToString().ToLowerInvariant());
            }
            return str.ToString(0, str.Length - 1);
        }

        private static Order[] StringToStort(string sort)
        {
            return new Order[0];
        }

        private static IList<Dimensions> DimensionsToString(string value)
        {
            IList<Dimensions> enums = new List<Dimensions>();
            if(value.Contains(","))
            {
                foreach (var c in value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
                {
                    enums.Add((Dimensions)Enum.Parse(typeof(Dimensions), c, true));
                }
            }
            else
            {
                enums.Add((Dimensions)Enum.Parse(typeof(Dimensions), value, true));
            }
            return enums;
        }

        private static IList<Metrics> MetricsToString(string value)
        {
            IList<Metrics> enums = new List<Metrics>();
            if (value.Contains(","))
            {
                foreach (var c in value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    enums.Add((Metrics)Enum.Parse(typeof(Metrics), c, true));
                }
            }
            else
            {
                enums.Add((Metrics)Enum.Parse(typeof(Metrics), value, true));
            }
            return enums;
        }
    }
}
